package com.asen.commons.core.base.controller;

import com.asen.commons.annotation.SkipAuthorization;
import com.asen.commons.constants.BaseConstant;
import com.asen.commons.core.base.domain.BaseEntry;
import com.asen.commons.core.base.domain.Core;
import com.asen.commons.core.base.dto.QueryDto;
import com.asen.commons.core.base.dto.RefQueryDto;
import com.asen.commons.core.base.service.BaseEntryService;
import com.asen.commons.core.base.service.CoreBaseService;
import com.asen.commons.core.base.service.CoreService;
import com.asen.commons.core.base.vo.DcPage;
import com.asen.commons.core.meta.dao.Condition;
import com.asen.commons.core.meta.dao.FilterGroup;
import com.asen.commons.core.meta.enums.FilterRelationEnum;
import com.asen.commons.core.meta.listener.PropertyProcessListener;
import com.asen.commons.core.base.result.ApiResult;
import com.asen.commons.core.utils.EntityUtils;
import com.asen.commons.utils.*;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 列表查询Controller
 *
 * @author Gary
 * @version 1.0
 * @date 2019-02-15
 */
@Slf4j
public class AbstractListController<T extends Core, D, V, S extends CoreService<T>> extends BaseController {

    @Value("${asen.performance.maxOverCost:1000}")
    protected long maxOverCost;

    /**
     * 相等参数字符
     */
    protected static final String EQUAL_PARAMS = "$EQUAL";

    @ApiOperation(value = "列表查询", httpMethod = BaseConstant.API_POST_METHOD)
    @PostMapping(value = "/list")
    public ApiResult<DcPage<V>> list(@RequestBody QueryDto<D> queryDto) throws Exception {
        long begin = System.currentTimeMillis();

        long cost = System.currentTimeMillis() - begin;
        if (cost > maxOverCost) {
            log.info("{} ==> buildCondition()耗时：{}毫秒", this.getClass().getName(), cost);
        }

        DcPage<V> pageInfo = query(queryDto);

        cost = System.currentTimeMillis() - begin;
        if (cost > maxOverCost) {
            log.info("{} ==> list()查询总耗时：{}毫秒", this.getClass().getName(), cost);
        }

        return success(pageInfo);
    }

    @SkipAuthorization
    @ApiOperation(value = "关联列表查询", httpMethod = BaseConstant.API_POST_METHOD)
    @PostMapping(value = "/refList")
    public ApiResult<DcPage<V>> refList(@RequestBody RefQueryDto<D> refQueryDto) throws Exception {
        long begin = System.currentTimeMillis();

        long cost = System.currentTimeMillis() - begin;
        if (cost > maxOverCost) {
            log.info("{} ==> buildCondition()耗时：{}毫秒", this.getClass().getName(), cost);
        }

        DcPage<V> pageInfo = refQuery(refQueryDto);

        cost = System.currentTimeMillis() - begin;
        if (cost > maxOverCost) {
            log.info("{} ==> list()查询总耗时：{}毫秒", this.getClass().getName(), cost);
        }

        return success(pageInfo);
    }

    @ApiOperation(value = "详情", httpMethod = BaseConstant.API_GET_METHOD)
    @RequestMapping(value = "/get")
    public ApiResult<V> get(Long id) throws Exception {
        V result = doGet(id);
        return success(result);
    }

    protected V doGet(Long id) throws Exception {
        V result = null;
        T model = getService().get(id);
        if (model != null) {
            result = newVo();
            buildVo(model, result);
        }
        return result;
    }

    @ApiOperation(value = "查询记录总条数", httpMethod = BaseConstant.API_POST_METHOD)
    @PostMapping(value = "/count")
    public ApiResult<Integer> count(@RequestBody QueryDto<D> queryDto) throws Exception {
        Condition condition = newCondition();
        buildCondition(condition, queryDto);

        // 构建通用过滤
        getService().buildCommonFilter(condition);

        condition.select("id");

        return success(getService().count(condition));
    }

    protected Condition newCondition() throws Exception {
        Condition condition = new Condition();
        return condition;
    }

    protected void buildCondition(final Condition condition, final D dto) throws Exception {
        condition.builder().build(dto, new PropertyProcessListener() {
            @Override
            public boolean processed(String propName) throws Exception {
                return buildCondition(condition, dto, propName);
            }
        });
    }

    /**
     * @param condition
     * @param dto
     * @param fieldName dto的属性名
     * @return
     * @throws Exception
     */
    protected boolean buildCondition(Condition condition, D dto, String fieldName) throws Exception {
        return false;
    }

    protected void buildConditionByParams(Condition condition, Map<String, Object> params) throws Exception {
        buildConditionByParamsByEntry(condition, params);
        condition.builder().build(params);
    }

    /**
     * 处理根据分录查询逻辑
     */
    protected void buildConditionByParamsByEntry(Condition condition, Map<String, Object> params) throws Exception {
        if (MapUtils.isEmpty(params)) {
            return;
        }
        Set<String> removeKeySet = new HashSet<>();
        Map<String, Map<String, Object>> entryParamsMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String key = entry.getKey();
            if (StringUtils.isBlank(key) || key.indexOf(".") <= 0) {
                continue;
            }
            String[] split = key.split("[.]");
            if (split.length != 2) {
                continue;
            }
            Object value = entry.getValue();
            if (value instanceof String && StringUtils.isBlank(value.toString())) {
                continue;
            }
            String entryName = split[0];
            String entryParams = split[1];
            if (entryParams.indexOf("$") < 0) {
                entryParams += EQUAL_PARAMS;
            }

            Map<String, Object> paramsMap = entryParamsMap.get(entryName);
            if (paramsMap == null) {
                paramsMap = new HashMap<>();
                entryParamsMap.put(entryName, paramsMap);
            }
            paramsMap.put(entryParams, entry.getValue());
            removeKeySet.add(key);
        }
        if (MapUtils.isNotEmpty(entryParamsMap)) {
            for (Map.Entry<String, Map<String, Object>> entry : entryParamsMap.entrySet()) {
                String entryName = entry.getKey();
                Map<String, Object> paramsMap = entry.getValue();
                if (MapUtils.isEmpty(paramsMap)) {
                    continue;
                }
                Condition entryCondition = new Condition();
                entryCondition.setPage(false);
                entryCondition.setPageNum(1);
                entryCondition.setPageSize(666);

                entryCondition.builder().build(paramsMap);
                // 根据entryName获取分录服务实现类对象,从分录服务获取出分录数据,然后取出parentId
                List<BaseEntry> entryList = null;

                // 获取entryService
                BaseEntryService entryService = getEntryService(entryName);
                // 获取到了entryService 并且 存在过滤条件
                if (entryService != null && entryCondition.getFilterItems().size() > 0) {
                    entryList = entryService.getList(entryCondition);
                }

                Set<Long> parentIds = new HashSet<>();
                if (CollectionUtils.isNotEmpty(entryList)) {
                    parentIds = entryList.stream().map(BaseEntry::getParentId)
                            .filter(IdUtils::isNotEmpty).collect(Collectors.toSet());
                }
                if (CollectionUtils.isNotEmpty(parentIds)) {
                    condition.in("id", parentIds);
                } else {
                    condition.equal("id", 0);
                }
            }
        }
        // 移除已匹配的key
        if (CollectionUtils.isNotEmpty(removeKeySet)) {
            for (String key : removeKeySet) {
                params.remove(key);
            }
        }
    }

    /**
     * 具体类可以自定义重写
     *
     * @param entryName
     * @return
     */
    protected BaseEntryService getEntryService(String entryName) throws Exception {
        if (getService() instanceof CoreBaseService) {
            return ((CoreBaseService) getService()).getEntryService(entryName);
        }
        return null;
    }

    protected void orderBy(Condition condition, QueryDto<D> queryDto) throws Exception {
        if (StringUtils.isNotEmpty(queryDto.getSortName())) {
            String sortName = queryDto.getSortName();
            if ("desc".equalsIgnoreCase(queryDto.getOrder())) {
                condition.orderByDesc(sortName);
            } else {
                condition.orderByAsc(sortName);
            }
        }

        if (StringUtils.isNotEmpty(queryDto.getOrderByAsc())) {
            condition.orderByAsc(queryDto.getOrderByAsc().split(","));
        }

        if (StringUtils.isNotEmpty(queryDto.getOrderByDesc())) {
            condition.orderByDesc(queryDto.getOrderByDesc().split(","));
        }

        if (CollectionUtils.isEmpty(condition.getOrderItems())) {
            defaultOrderBy(condition, queryDto);
        }
    }

    protected void defaultOrderBy(Condition condition, QueryDto<D> queryDto) throws Exception {
        condition.orderByDesc("id");
    }

    protected void buildCondition(Condition condition, QueryDto<D> queryDto) throws Exception {
        condition.setPage(queryDto.getIsPage());
        condition.setPageNum(queryDto.getPageNum());
        condition.setPageSize(queryDto.getPageSize());

        if (StringUtils.isNotEmpty(queryDto.getCollectBy())) {
            Arrays.asList(queryDto.getCollectBy().split(",")).forEach(item -> {
                condition.collectBy(item);
            });
        }

        select(condition, queryDto);
        ignoreSelect(condition, queryDto);
        orderBy(condition, queryDto);
        buildCondition(condition, queryDto.getData());
        buildConditionByParams(condition, queryDto.getParams());
    }

    protected void select(final Condition condition, final QueryDto<D> queryDto) throws Exception {
        if (getSelectItems() == null) {
            return;
        }

        getSelectItems().forEach(item -> {
            condition.addSelect(item);
        });
    }

    protected List<String> getSelectItems() throws Exception {
        return null;
    }

    protected void ignoreSelect(final Condition condition, final QueryDto<D> queryDto) throws Exception {
        List<String> ignoreSelectList = getIgnoreSelect(queryDto);
        if (CollectionUtils.isNotEmpty(ignoreSelectList)) {
            ignoreSelectList.forEach(condition::selectExclude);
        }
    }

    protected List<String> getIgnoreSelect(QueryDto<D> queryDto) throws Exception {
        return null;
    }

    /**
     * 查询
     *
     * @param queryDto
     * @return
     * @throws Exception
     */
    protected DcPage<V> query(final QueryDto<D> queryDto) throws Exception {
        Condition condition = newCondition();
        buildCondition(condition, queryDto);
        return query(condition);
    }

    /**
     * 关联查询
     *
     * @param refQueryDto
     * @return
     * @throws Exception
     */
    protected DcPage<V> refQuery(final RefQueryDto<D> refQueryDto) throws Exception {
        Condition condition = newCondition();
        buildCondition(condition, refQueryDto);
        if (StringUtils.isNotEmpty(refQueryDto.getRefSearchKey()) && CollectionUtils.isNotEmpty(refQueryDto.getRefSearchFieldList())) {
            FilterGroup group = new FilterGroup(FilterRelationEnum.OR);
            refQueryDto.getRefSearchFieldList().forEach(item -> {
               group.like(item, refQueryDto.getRefSearchKey());
            });
            condition.addFilterGroup(group);
        }
        return query(condition);
    }

    /**
     * 查询
     *
     * @param condition
     * @return
     * @throws Exception
     */
    protected DcPage<V> query(Condition condition) throws Exception {
        List<V> voList = null;

        int count = 0;
        int pageSize = 0;
        int pageNum = 0;

        // 构建通用过滤
        getService().buildCommonFilter(condition);

        if (condition.isPage()) {
            long begin = System.currentTimeMillis();
            count = getService().count(condition);
            long end = System.currentTimeMillis();
            logger.debug("count耗时：" + (end - begin));

            pageSize = condition.getPageSize();
            pageNum = condition.getPageNum();
            if (count > 0) {
                begin = System.currentTimeMillis();
                List<T> modelList = getService().getList(condition);
                end = System.currentTimeMillis();
                logger.debug("getList耗时：" + (end - begin));

                begin = System.currentTimeMillis();

                voList = toVoList(modelList);

                initVoList(voList, condition);

                end = System.currentTimeMillis();
                logger.debug("vo转换耗时：" + (end - begin));
            }
        } else {
            condition.setPageSize(999999);
            condition.setPageNum(1);
            long begin = System.currentTimeMillis();
            List<T> modelList = getService().getList(condition);
            count = getService().count(condition);
            long end = System.currentTimeMillis();
            logger.debug("getList耗时：" + (end - begin));
            begin = System.currentTimeMillis();

            voList = toVoList(modelList);

            end = System.currentTimeMillis();
            logger.debug("vo转换耗时：" + (end - begin));
        }

        DcPage<V> pageInfo = new DcPage<V>(voList, count, pageNum, pageSize);
        return pageInfo;
    }

    /**
     * 个性化查询
     *
     * @param condition
     * @param service
     * @param voClazz
     * @param <K>
     * @return
     * @throws Exception
     */
    protected <K> DcPage<K> query(Condition condition, CoreService service, Class<K> voClazz) throws Exception {
        List list = service.getList(condition);

        int count = 0;
        int pageSize = 0;
        int pageNum = 0;
        if (condition.isPage()) {
            count = service.count(condition);
            pageSize = condition.getPageSize();
            pageNum = condition.getPageNum();
        }

        List<K> voList = new ArrayList<K>();
        for (Object model : list) {
            K vo = EntityUtils.convert(voClazz, model);
            voList.add(vo);
        }

        DcPage<K> pageInfo = new DcPage<K>(voList, count, pageNum, pageSize);
        return pageInfo;
    }

    protected List<V> toVoList(List<T> list) throws Exception {
        if (getDoClass().equals(getVoClass())) {
            return (List<V>) list;
        }

        List<V> voList = new ArrayList<V>();
        for (T model : list) {
            V vo = newVo();
            buildVo(model, vo);
            voList.add(vo);
        }
        return voList;
    }

    protected void initVoList(List<V> voList, Condition condition) throws Exception {

    }

    protected void buildVo(final T model, final V vo) throws Exception {
        EntityUtils.copyProperties(model, vo, new PropertyProcessListener() {
            @Override
            public boolean processed(String propName) throws Exception {
                return buildVo(model, vo, propName);
            }
        }, true);
    }

    /**
     * 返回true，覆盖默认逻辑
     *
     * @param model
     * @param vo
     * @param fieldName model的属性名
     * @return
     * @throws Exception
     */
    protected boolean buildVo(T model, V vo, String fieldName) throws Exception {
        return false;
    }

    protected V newVo() throws Exception {
        return (V) getVoClass().newInstance();
    }

    @SuppressWarnings("unchecked")
    protected Class<T> getDoClass() {
        if (doClazz != null) {
            return doClazz;
        }

        ParameterizedType superClassType = (ParameterizedType) this.getClass().getGenericSuperclass();

        if (!ArrayUtils.isEmpty(superClassType.getActualTypeArguments())) {
            doClazz = (Class<T>) superClassType.getActualTypeArguments()[0];
        }

        return doClazz;
    }

    @SuppressWarnings("unchecked")
    protected Class<D> getDtoClass() {
        if (dtoClazz != null) {
            return dtoClazz;
        }

        ParameterizedType superClassType = (ParameterizedType) this.getClass().getGenericSuperclass();

        if (!ArrayUtils.isEmpty(superClassType.getActualTypeArguments())) {
            dtoClazz = (Class<D>) superClassType.getActualTypeArguments()[1];
        }
        return dtoClazz;
    }

    @SuppressWarnings("unchecked")
    protected Class<V> getVoClass() {
        if (voClazz != null) {
            return voClazz;
        }

        ParameterizedType superClassType = (ParameterizedType) this.getClass().getGenericSuperclass();

        if (!ArrayUtils.isEmpty(superClassType.getActualTypeArguments())) {
            voClazz = (Class<V>) superClassType.getActualTypeArguments()[2];
        }

        return voClazz;
    }

    @SuppressWarnings("unchecked")
    protected Class<S> getServiceClass() {
        if (serviceClazz != null) {
            return serviceClazz;
        }

        ParameterizedType superClassType = (ParameterizedType) this.getClass().getGenericSuperclass();

        if (!ArrayUtils.isEmpty(superClassType.getActualTypeArguments())) {
            serviceClazz = (Class<S>) superClassType.getActualTypeArguments()[3];
        }

        return serviceClazz;
    }

    private Object lock = new Object();

    protected S getService() {
        if (service != null) {
            return service;
        }

        synchronized (lock) {
            if (service == null) {
                //String modelClass = getDoClass().getName();
                //service = (CoreService<T>) BillTypeUtils.getServiceBean(modelClass);
                //BaseAssert.notNull(service, "没有关联的的服务Bean，modelClass：" + modelClass);

                service = SpringUtil.getBean(getServiceClass());
            }
        }

        return service;
    }

    private Class<T> doClazz;

    private Class<D> dtoClazz;

    private Class<V> voClazz;

    private Class<S> serviceClazz;

    private S service;
}
